from tw.forms.validators import Int, NotEmpty,UnicodeString,All,ForEach,DateConverter,FancyValidator
from lifepyscle.model import *
from lifepyscle.model import DBSession
from formencode import Invalid,Schema
from formencode.validators import FormValidator
from mx import DateTime as mxdt
from datetime  import *
import re
_ = lambda s: s # dummy

class StartBeforeEnd(FormValidator):

    """
    Check that the end date is not before the start date
    """
    show_match = False
    field_names = None
    validate_partial_form = True
    __unpackargs__ = ('*', 'field_names')

    messages = {
        'invalid': _("Fields do not match (should be %(match)s)"),
        'invalidNoMatch': _("Fields do not match"),
        'notDict': _("Fields should be a dictionary"),
        'endPrecedesStart':_("The end date cannot occur before the start date"),
        'invalidValues':_("The values provided were invalid (only d,w,m,y are supported)"),
        }

    def __init__(self, *args, **kw):
        super(FormValidator, self).__init__(*args, **kw)
        if len(self.field_names) < 2:
            raise TypeError("FieldsMatch() requires at least two field names")

    def validate_partial(self, field_dict, state):
        for name in self.field_names:
            if not field_dict.has_key(name):
                return
        self.validate_python(field_dict, state)

    def validate_python(self, field_dict, state):
        try:
            ref = field_dict[self.field_names[0]]
        except TypeError:
            # Generally because field_dict isn't a dict
            raise Invalid(self.message('notDict', state), field_dict, state)
        except KeyError:
            ref = ''
        errors = {}
        # we only ever have two fields
        startval = field_dict['starts'] # this is already datetime
        if isinstance(startval,unicode):
            try:
                startval = datetime.strptime(startval,'%Y-%m-%d')
            except Exception,e:
                print e
                raise Invalid(self.message('invalidValues', state), field_dict,state)
        
        end = self.field_names[1] # the fieldname is 'ends'
        endvalue = field_dict.get(end,'')
        field_dict['ends_str'] = endvalue
        if len(endvalue)==0 or endvalue == None:
            endvalue = startval
            field_dict['ends'] = startval
        else:
            try:
                rv = datetime.strptime(endvalue,"%Y-%m-%d")
            except:
                split_underscore = endvalue.split("_")
                if len(split_underscore)!=2:
                    raise Invalid(self.message('invalidValues', state), field_dict,state)
                else:
                    if not split_underscore[0].isdigit():
                        raise Invalid(self.message('notNumeric', state), field_dict, state)
                    else:
                        the_range = int(split_underscore[0])
                    if split_underscore[1].lower() not in ['d','days','w','weeks','m','months','y','years']:
                        raise Invalid(self.message('invalid_period',state),field_dict,state)
                    else:
                        s = split_underscore[1].lower()
                        if s == 'w' or s == 'weeks':
                            rv =  (the_range,'w')
                        elif s == 'd' or s == 'days':
                            rv = (the_range,'d')
                        elif s == 'm' or s == 'months':
                            rv =  (the_range,'m')
                        elif s == 'y' or s == 'years':
                            rv = (the_range,'y')
                        else:
                            raise Invalid(self.message('invalidValues', state), field_dict, state)
            if isinstance(rv,tuple): # e.g. (3,u'm')
                st1 = mxdt.Date(startval.year,startval.month,startval.day)
                if rv[1] == 'd':
                    futuredate = st1 + mxdt.RelativeDateTime(days=+rv[0])
                elif rv[1] == 'w':
                    futuredate = st1 + mxdt.RelativeDateTime(weeks=+rv[0])
                elif rv[1] == 'm':
                    futuredate = st1 + mxdt.RelativeDateTime(months=+rv[0])
                elif rv[1] == 'y':
                    futuredate = st1 + mxdt.RelativeDateTime(years=+rv[0])
                elif rv[1] == '' and rv[0] == 0:
                    # this means the field was empty - we take the end date to be the same as start
                    futuredate = st1
                else:
                    raise Invalid(self.message('invalidValues', state), field_dict, state)
                """ SQLALCHEMY CAN ONLY HANDLE DATETIME """
                # FIXME: a horrible, horrible hack
                field_dict[end] = datetime.date(datetime.strptime("%s-%s-%s"%(futuredate.year,futuredate.month,futuredate.day),"%Y-%m-%d"))
            else:
                # rv should be a mxdt.DateTime instance
                field_dict[end] = rv
                if field_dict[end] < startval:
                    errors[end] = self.message('endPrecedesStart',state,match=ref)
        if errors:
            error_list = errors.items()
            error_list.sort()
            error_message = '<br>\n'.join(
                ['%s: %s' % (name, value) for name, value in error_list])
            raise Invalid(error_message,
                          field_dict, state,
                          error_dict=errors)

class DateCompare(FancyValidator):
    """
    This was taken from:
    http://blog.tplus1.com/index.php/2008/11/25/use-formencode-to-verify-one-date-precedes-another/
    """
    messages = dict(invalid="Start date must be before end date")

    def validate_python(self, field_dict, state):

        start_date = field_dict['start_date']
        end_date = field_dict['end_date']

        if isinstance(end_date,tuple):
            print "I have a tuple"
        else: # we already have a DateTime object
            if start_date > stop_date:
                msg = self.message('invalid', state)
                raise Invalid(msg, field_dict, state,
                    error_dict=dict(stop_date=msg))


class UniqueParty(FancyValidator):
    min = 3
    non_letter = 1
    messages = {
        'name_exists': 'That party name already exists'
    }
    def _to_python(self, value, state):
        # _to_python gets run before validate_python.  Here we
        # strip whitespace off the password, because leading and
        # trailing whitespace in a password is too elite.
        return value.strip()

    def validate_python(self, value, state):
        parties = DBSession.query(Party.name).filter(Party.name == value).all()
        if len(parties) > 0:
            raise Invalid(self.message("name_exists", state,min=self.min),value, state)

class AllValidEmails(FancyValidator):
    reggie = re.compile(r"(?:^|\s)[-a-z0-9_.]+@(?:[-a-z0-9]+\.)+[a-z]{2,6}(?:\s|$)",re.IGNORECASE)
    def _to_python(self, value, state):
        # _to_python gets run before validate_python.  Here we
        # strip whitespace off the password, because leading and
        # trailing whitespace in a password is too elite.
        return value.strip()

    def validate_python(self, value, state):
        pot_mails = value.split(",")
        for x in pot_mails:
            raw_mail = x.strip()
            print raw_mail
            ok = re.match(self.reggie,raw_mail)
            try:
                valid_mail = ok.group()
            except AttributeError:
                print "%s is a shit mail"%x
                raise Invalid("%s is not a valid mail"%x,value,state)


class DateRange(FancyValidator):
    # Allow YYYY-MM-DD as well
    min = 2
    non_letter = 1
    messages = {
        'invalid_period': 'That is not a valid period. Valid periods are d,w,m,y (days, weeks, months, years)',
        'missing_period': 'No period provided. Valid periods are d,w,m,y (days, weeks, months, years)',
        'invalidValues': 'For a relative date, use a number followed by an underscore "_" followed by a period. For 2 weeks: 2_w',
        'notNumeric': 'The number (of days,weeks,months,years) must be the first value. It should be followed by an underscore'
    }

    def _to_python(self, value, state):
        # this gets called first
        if len(value) > 10:
            value = value[:9]
        try:
            valdate = mxdt.strptime(value,"%Y-%m-%d")
        except:
            # quickly check if we were sent an empty field
            if len(value)==0:
                return (0,'')
            else:
                split_underscore = value.split("_")
                if len(split_underscore)!=2:
                    raise Invalid(self.message('invalidValues', state), value,state)
                else:
                    if not split_underscore[0].isdigit():
                        raise Invalid(self.message('notNumeric', state), value, state)
                    else:
                        the_range = int(split_underscore[0])
                if split_underscore[1].lower() not in ['d','days','w','weeks','m','months','y','years']:
                    raise Invalid(self.message('invalid_period',state),value,state)
                else:
                    s = split_underscore[1].lower()
                    if s == 'w' or s == 'weeks':
                        rv =  (the_range,'w')
                    elif s == 'd' or s == 'days':
                        rv = (the_range,'d')
                    elif s == 'm' or s == 'months':
                        rv =  (the_range,'m')
                    elif s == 'y' or s == 'years':
                        rv = (the_range,'y')
                    else:
                        raise Invalid(self.message('invalidValues', state), value, state)
                    value = rv
                    return rv
        else:
            return valdate

    def validate_python(self, value, state):
        """ because we already did the validation work as part of converting to python,
           there is nothing to do here except to return what was passed through from _to_python """
        return value

